﻿using System;
using System.Linq;
using System.Collections.Generic;

namespace DotNetNeural.Data.Utils
{
    public static class ArrayExtensions
    {
        public const float DefaultEqualityDelta = 0.00000001f;

        /// <summary>
        /// Checks if all elements of array satisfy given condition
        /// </summary>
        public static bool IsTrueForEach<T>(this T[,] source, Predicate<T> condition)
        {
            int dim1 = source.GetLength(0);
            int dim2 = source.GetLength(1);

            for (int i = 0; i < dim1; ++i)
            {
                for (int j = 0; j < dim2; ++j)
                {
                    if (!condition(source[i, j]))
                        return false;
                }
            }

            return true;
        }

        public static bool IsTrueForEach<T>(this T[] source, Predicate<T> condition)
        {
            for (int i = 0; i < source.Length; ++i)
            {
                if (!condition(source[i]))
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Sets all elements of matrix to given value
        /// </summary>
        public static void SetForEach<T>(this T[,] source, T value)
        {
            int dim1 = source.GetLength(0);
            int dim2 = source.GetLength(1);

            for (int i = 0; i < dim1; ++i)
            {
                for (int j = 0; j < dim2; ++j)
                {
                    source[i, j] = value;
                }
            }
        }

        /// <summary>
        /// Sets all elements of array to given value
        /// </summary>
        public static void SetForEach<T>(this T[] source, T value)
        {
            for (int i = 0; i < source.Length; ++i)
            {
                source[i] = value;
            }
        }

        /// <summary>
        /// Converts given multidimensional array to IEnumerable version
        /// </summary>
        public static IEnumerable<T[]> ToEnumerable<T>(this T[,] source)
        {
            int dim = source.GetLength(0);
            int dim2 = source.GetLength(1);

            List<T[]> result = new List<T[]>();

            for (int i = 0; i < dim; ++i)
            {
                T[] arr = new T[dim2];
                for (int j = 0; j < dim2; ++j)
                {
                    arr[j] = source[i, j];
                }

                result.Add(arr);
            }

            return result;
        }

        /// <summary>
        /// Checks whether this value is equal to target value with defined precision
        /// </summary>
        public static bool IsEqual(this float source, float target, float precision)
        {
            return Math.Abs(source - target) < precision;
        }

        /// <summary>
        /// Checks whether this value is equal to target value with 0.0001f precision
        /// </summary>
        public static bool IsEqual(this float source, float target)
        {
            return IsEqual(source, target, DefaultEqualityDelta);
        }

        /// <summary>
        /// Compares two collections element by element. Order of elements is taken into account
        /// </summary>
        public static bool Compare(this IEnumerable<float[]> source, IEnumerable<float[]> target)
        {
            if (target == null)
                return false;

            if (source.Count() != target.Count())
                return false;

            var array1 = source.ToArray();
            var array2 = target.ToArray();

            for (int i = 0; i < array1.Length; ++i)
            {
                float[] i1 = array1[i];
                float[] i2 = array2[i];
                if (!i1.Compare(i2, DefaultEqualityDelta))
                    return false;
            }

            return true;
        }

        public static bool Compare(this float[,] source, float[,] target, float precision)
        {
            if (target == null)
                return false;

            int dim1 = source.GetLength(0),
                dim2 = source.GetLength(1);

            if (dim1 != target.GetLength(0) ||
                 dim2 != target.GetLength(1))
                return false;

            for (int i = 0; i < dim1; ++i)
            {
                for (int j = 0; j < dim2; ++j)
                {
                    if (Math.Abs(source[i, j] - target[i, j]) > precision)
                        return false;
                }
            }

            return true;
        }

        public static bool Compare(this float[] source, float[] target, float precision)
        {
            if (target == null)
                return false;

            if (source.Length != target.Length)
                return false;

            for (int i = 0; i < source.Length; ++i)
            {
                if (Math.Abs(source[i] - target[i]) > precision)
                    return false;
            }

            return true;
        }

        public static void Assign(this float[,] target, float[,] source)
        {
            int dim1 = target.GetLength(0);
            int dim2 = target.GetLength(1);

            int s_dim1 = source.GetLength(0);
            int s_dim2 = source.GetLength(1);

            if (s_dim1 > dim1 || s_dim2 > dim2)
                throw new ArgumentException("Source matrix is bigger then target one");


            for (int i = 0; i < s_dim1; ++i)
            {
                for (int j = 0; j < s_dim2; ++j)
                {
                    target[i, j] = source[i, j];
                }
            }
        }

        /// <summary>
        /// Shuffles given collection using Fisher-Yates algorithm
        /// </summary>
        public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
        {
            return source.Shuffle(new Random());
        }

        #region Private methods

        private static IEnumerable<T> Shuffle<T>(
            this IEnumerable<T> source, Random rnd)
        {
            if (source == null || rnd == null)
                throw new NullReferenceException("Illegal null-reference arguments");

            return source.ShuffleIterator(rnd);
        }

        private static IEnumerable<T> ShuffleIterator<T>(
            this IEnumerable<T> source, Random rnd)
        {
            T[] buffer = source.ToArray();
            for (int n = 0; n < buffer.Length; n++)
            {
                int k = rnd.Next(n, buffer.Length);
                yield return buffer[k];

                buffer[k] = buffer[n];
            }
        }

        #endregion
    }
}
